package io.github.wslxm.springbootplus2.manage.sys.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.wslxm.springbootplus2.core.result.ResultType;
import io.github.wslxm.springbootplus2.core.utils.validated.XjValidUtil;
import io.github.wslxm.springbootplus2.manage.sys.model.vo.next.SysNumberGcVO;
import io.github.wslxm.springbootplus2.starter.redis.lock.XjDistributedLock;
import io.github.wslxm.springbootplus2.starter.redis.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import io.github.wslxm.springbootplus2.core.base.model.BasePage;
import io.github.wslxm.springbootplus2.core.base.service.impl.BaseServiceImpl;
import io.github.wslxm.springbootplus2.core.utils.XjBeanUtil;
import io.github.wslxm.springbootplus2.core.utils.tree.XjTreeUtil;
import io.github.wslxm.springbootplus2.manage.sys.mapper.SysNumberMapper;
import io.github.wslxm.springbootplus2.manage.sys.service.SysNumberService;
import io.github.wslxm.springbootplus2.manage.sys.model.entity.SysNumber;
import io.github.wslxm.springbootplus2.manage.sys.model.vo.SysNumberVO;
import io.github.wslxm.springbootplus2.manage.sys.model.dto.SysNumberDTO;
import io.github.wslxm.springbootplus2.manage.sys.model.query.SysNumberQuery;
import org.apache.commons.lang3.StringUtils;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * 编号生成表 ServiceImpl
 *
 * <p>
 * ::本代码由[兮家小二]提供的代码生成器生成,如有问题,请手动修改 ::作者CSDN:https://blog.csdn.net/qq_41463655
 * </p>
 * @author ws
 * @email 1720696548@qq.com
 * @date 2023-12-06 11:08:13
 */
@Service
@Slf4j
public class SysNumberServiceImpl extends BaseServiceImpl<SysNumberMapper, SysNumber> implements SysNumberService {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public List<SysNumberVO> tree(SysNumberQuery query) {
        LambdaQueryWrapper<SysNumber> queryWrapper = new LambdaQueryWrapper<SysNumber>().orderByDesc(SysNumber::getCreateTime);
        List<SysNumber> list = this.list(queryWrapper);
        List<SysNumberVO> listVo = XjBeanUtil.listVo(list, SysNumberVO.class);
        return listVo.size() > 0 ? XjTreeUtil.nextTree(listVo, "0").getChildren() : listVo;
    }


    @Override
    public BasePage<SysNumberVO> findPage(SysNumberQuery query) {
        LambdaQueryWrapper<SysNumber> queryWrapper = new LambdaQueryWrapper<SysNumber>().orderByDesc(SysNumber::getCreateTime);
        queryWrapper.eq(query.getHierarchy() != null, SysNumber::getHierarchy, query.getHierarchy());
        queryWrapper.eq(StringUtils.isNotBlank(query.getPid()), SysNumber::getPid, query.getPid());
        queryWrapper.likeRight(StringUtils.isNotBlank(query.getName()), SysNumber::getName, query.getName());
        queryWrapper.likeRight(StringUtils.isNotBlank(query.getCode()), SysNumber::getCode, query.getCode());
        Page<SysNumber> page = this.page(new Page<>(query.getCurrent(), query.getSize()), queryWrapper);
        return XjBeanUtil.pageVo(page, SysNumberVO.class);
    }

    @Override
    public SysNumberVO findId(String id) {
        return XjBeanUtil.convert(this.getById(id), SysNumberVO.class);
    }

    @Override
    public String insert(SysNumberDTO dto) {
        this.verifyCodeRepeat(dto.getCode(), null);
        SysNumber entity = dto.convert(SysNumber.class);
        boolean b = this.save(entity);
        return entity.getId();
    }

    @Override
    public boolean upd(String id, SysNumberDTO dto) {
        this.verifyCodeRepeat(dto.getCode(), id);
        SysNumber entity = dto.convert(SysNumber.class);
        entity.setId(id);
        return this.updateById(entity);
    }

    @Override
    public boolean del(String id) {
        List<SysNumber> list = this.list(new LambdaQueryWrapper<SysNumber>().select(SysNumber::getId, SysNumber::getPid));
        List<SysNumberVO> listVos = XjBeanUtil.listVo(list, SysNumberVO.class);
        List<String> nextIds = XjTreeUtil.getNextIds(listVos, id);
        nextIds.add(id);
        return this.removeBatchByIds(nextIds);
    }


    @Override
    @XjDistributedLock(lockName = "'xj-sys-number_'+ #code",waitTime = 5L)
    public String generateNumber(String code) {
        SysNumber number = this.getOne(new LambdaQueryWrapper<SysNumber>()
                .select(SysNumber::getId, SysNumber::getCode, SysNumber::getPrefix, SysNumber::getTimeFormat, SysNumber::getIncrement, SysNumber::getLastKey, SysNumber::getInitIncrement, SysNumber::getIncrementLen)
                .eq(SysNumber::getCode, code));
        LocalDateTime now = LocalDateTime.now();
        SysNumberGcVO gcNumberVO = this.getNo(
                number.getLastKey(),
                number.getCode(),
                number.getPrefix(),
                number.getTimeFormat(),
                number.getInitIncrement(),
                number.getIncrement(),
                number.getIncrementLen()
        );
        // 缓存数据更新
        number.setIncrement(gcNumberVO.getIncrement());
        number.setLastNo(gcNumberVO.getNo());
        number.setLastTime(now);
        number.setLastKey(gcNumberVO.getRedisKey());
        this.updateById(number);
        return gcNumberVO.getNo();
    }

    /**
     * 获取唯一编号
     * <P>
     *    分布式架构获取唯一编号（基于redis）--> 订单号，交易号，退款号等等
     *    如果需要 redis 集群,需设置自增歩长,请使用命令设置或使用其他方法规避
     * </P>
     *
     * @param lastKey  上一次生成编号的 redisKey
     * @param code  缓存 code (相同 code+时间下的 delta 使用所有自增, 当时间变化 delta 自增值则重新开始计算)
     * @param prefix  编号前缀 实际编号开头

     * @param format 时间格式: 自定义格式 -> yyyyMMddHHmmssSSS
     * @param initIncrement  初始自增值
     * @param increment      当前自增值
     * @param incrementLen   自增值位数
     * @return java.lang.String
     * @author ws
     * @mail 1720696548@qq.com
     * @date 2020/2/20 0020 15:59
     */
    private SysNumberGcVO getNo(String lastKey, String code, String prefix, String format, Long initIncrement, Long increment, Integer incrementLen) {
        // 生成14位的时间戳(每秒使用新的时间戳当key)
        String time = new SimpleDateFormat(format).format(new Date());
        // 获得 redisKey
        String redisKey = code + ":" + time;
        if (redisKey.equals(lastKey)) {
            // 同一时间段
            // 1、获取自增值
            redisTemplate.opsForValue().set(redisKey, increment);
            increment = redisTemplate.opsForValue().increment(redisKey, 1L);
        } else {
            // 不同时间段
            // 1、删除该code 下的其他 redisKey
            Set<String> listKey = redisUtil.getKeysByPrefix(code);
            redisUtil.delete(listKey);
            // 2、获取自增值
            redisTemplate.opsForValue().set(redisKey, initIncrement);
            increment = redisTemplate.opsForValue().increment(redisKey, 1L);
        }
        // 获取订单号，时间戳 + 唯一自增Id(6位数,不过前方补0)
        String no = prefix + time + String.format("%0" + incrementLen + "d", increment);
        SysNumberGcVO vo = new SysNumberGcVO();
        vo.setNo(no);
        vo.setIncrement(increment);
        vo.setRedisKey(redisKey);
        return vo;
    }


    /**
     * 部门code重复验证
     *
     * @author wangsong
     * @mail 1720696548@qq.com
     * @date 2022/8/20 0020 14:33 验证
     * @version 1.0.0
     */
    private void verifyCodeRepeat(String code, String excludeId) {
        long count = this.count(new LambdaQueryWrapper<SysNumber>()
                .eq(SysNumber::getCode, code)
                .ne(excludeId != null, SysNumber::getId, excludeId)
        );
        XjValidUtil.isTrue(count > 0, ResultType.BASE_CODE_REPEAT);
    }
}
